/*******************************************************************************************
 *	Copyright (c) 2016, zzg.zhou(11039850@qq.com)
 * 
 *  Monalisa is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU Lesser General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.

 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU Lesser General Public License for more details.

 *	You should have received a copy of the GNU Lesser General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************************/
package com.tsc9526.monalisa.orm.datasource;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.DataSource;

import com.google.common.base.Strings;
import com.tsc9526.monalisa.orm.annotation.DB;
import com.tsc9526.monalisa.orm.dialect.Dialect;
import com.tsc9526.monalisa.orm.dialect.MysqlDialect;
import com.tsc9526.monalisa.orm.dialect.OracleDialect;
import com.tsc9526.monalisa.orm.dialect.PostgresDialect;
import com.tsc9526.monalisa.orm.dialect.SQLServerDialect;
import com.tsc9526.monalisa.tools.Tasks;
import com.tsc9526.monalisa.tools.cache.CacheManager;
import com.tsc9526.monalisa.tools.clazz.MelpClass;
import com.tsc9526.monalisa.tools.io.MelpClose;
import com.tsc9526.monalisa.tools.logger.Logger;

/**
 * 
 * @author zzg.zhou(11039850@qq.com)
 */
public class DataSourceManager {
	static Logger logger=Logger.getLogger(DataSourceManager.class);
	
	private static DataSourceManager dm=new DataSourceManager();
	
	public static DataSourceManager getInstance(){
		return dm;
	}	
	 
	/**
	 * close data source, daemon thread ...
	 */
	public static void shutdown(){
		Tasks.instance.destory();
		CacheManager.getInstance().shutdown();
	}
	 
	private Map<String, DBConfig> dss=new ConcurrentHashMap<String,DBConfig>();
	
	private Map<String, Dialect> dialects=new ConcurrentHashMap<String,Dialect>();
	
	
	private DataSourceManager(){
		registerShutdown();
		
		registerDialect(new MysqlDialect());
		registerDialect(new SQLServerDialect());
		registerDialect(new OracleDialect());
		registerDialect(new PostgresDialect());
	}
	
	private void registerShutdown(){
		Tasks.instance.addShutdown(new Runnable() {
			public void run(){
				Set<String> jdbsURLs=new LinkedHashSet<String>();
				jdbsURLs.add("jdbc:relique:csv:monalisa-memory");
				
				for(DBConfig cfg:dss.values()){
					cfg.close();
				}
				
				for(String url:jdbsURLs){
					try{
						DriverManager.deregisterDriver(DriverManager.getDriver(url));
					}catch(SQLException e){
						MelpClose.close(e);
					}
				}
				 
				shutdownMysqlThreads();
			}
		});
	}
	
	private void shutdownMysqlThreads(){
		try {
		    Class<?> cls=MelpClass.forName("com.mysql.jdbc.AbandonedConnectionCleanupThread");
		    Method   mth=(cls==null ? null : cls.getMethod("shutdown"));
		    if(mth!=null) {
		        mth.invoke(null);
		    }
		}catch(ClassNotFoundException e){
			MelpClose.close(e);
		}catch (Exception t) {
			logger.error(""+t,t);
		}
	}
	
	public void registerDialect(String dialectClass)throws Exception{
		Class<Dialect> cd=(Class<Dialect>)MelpClass.forName(dialectClass);
		registerDialect(cd.newInstance());
	}
	
	public void registerDialect(Dialect dialect){
		dialects.put(dialect.getUrlPrefix(), dialect);
	}
	
	public void unregisterDialect(Dialect dialect){
		dialects.remove(dialect.getUrlPrefix());
	}

	public DBConfig getDBConfig(Class<?> clazzWithDBAnnotation) {
		Class<?> dbClass = MelpClass.findClassWithAnnotation(clazzWithDBAnnotation, DB.class);
		if (dbClass == null) {
			throw new RuntimeException("Class without @DB: " + clazzWithDBAnnotation);
		}

		DB db = dbClass.getAnnotation(DB.class);
		String dbKey = db.key();
		if (Strings.isNullOrEmpty(dbKey)) {
			dbKey = dbClass.getName();
		}
		return getDBConfig(dbKey, db);
	}

	public DBConfig getDBConfig(String dbKey, DB db, boolean reInit) {
		DBConfig cfg = dss.get(dbKey);
		if (cfg == null) {
			cfg = getDBConfig(dbKey, db);
		} else if (reInit) {
			cfg.init(db);
		}
		return cfg;
	}

	public DBConfig getDBConfig(String dbKey, DB db, String cfgBasePath) {
		DBConfig cfg = dss.get(dbKey);
		if (cfg == null) {
			cfg = getDBConfig(dbKey, db);
		}
		cfg._cfg.cfgBasePath = cfgBasePath;
		return cfg;
	}


	/**
	 * 获取 数据库 数据源 配置
	 * @param dbKey
	 * @param db
	 * @return
	 */
	public DBConfig getDBConfig(String dbKey, DB db) {
		DBConfig cfg = dss.get(dbKey);
		if (cfg == null) {
			Properties poolProperties = new Properties();
			poolProperties.setProperty("driver", db.driver());
			poolProperties.setProperty("url", db.url());
			poolProperties.setProperty("username", db.username());
			poolProperties.setProperty("password", db.password());

			DataSource ds = new SimpleDataSource(poolProperties);
			cfg = new DBConfig(dbKey, ds, db);
			cfg.init();
			putDBConfig(dbKey, cfg);
		}
		return cfg;
	}

	
	public DBConfig findDbConfig(String dbKey){
		return dss.get(dbKey);
	}
	
	void putDBConfig(String dbKey,DBConfig cfg){
		dss.put(dbKey, cfg);
	}
	
	public DataSource getDataSource(DBConfig db){
		if(db==null){
			throw new RuntimeException("DB cannot be null");
		}
	 
		return db.getDataSource();
	}
	
	public Dialect getDialect(DBConfig db){
		return db.getDialect();
	}

	public DBConfig getDBConfig(String dbKey, DataSource ds) {
		DBConfig cfg = dss.get(dbKey);
		if (cfg == null) {
			cfg = new DBConfig(ds);
			putDBConfig(dbKey, cfg);
		}
		return cfg;
	}
}
